package com.puzzlebazar.client.main.model;

import java.util.List;

import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.puzzlebazar.client.puzzleinfo.PuzzleInfo;
import com.puzzlebazar.client.services.PuzzleServiceAsync;
import com.puzzlebazar.client.services.ServiceFactory;
import com.puzzlebazar.client.user.UserModel;
import com.puzzlebazar.client.util.Observable;
import com.puzzlebazar.client.util.ObservableImpl;
import com.puzzlebazar.client.util.Observer;

public class PuzzleList extends ObservableImpl implements Observer {

	private final PopulateCallback populateCallback = new PopulateCallback(); 
	private final DeletionCallback deletionCallback = new DeletionCallback(); 
	
	private List<PuzzleInfo> puzzleList = null;
	private boolean refreshing = false;
	private PuzzleListInfo listInfo = null;

	private final UserModel model;	 // TODO Do we really need this?
	
	/**
	 * Creates a new puzzle list associated with a given model. The association
	 * makes it possible to refresh the puzzle list when the logged-in user switches
	 * 
	 * @param model The associated model
	 */
	public PuzzleList( UserModel model ) {
		this.model = model;
		
		model.addObserver( this );
	}
	
	/**
	 * @return True if the puzzle list is valid, false otherwise
	 */
	public boolean isValid() {
		return puzzleList != null;
	}
	
	/**
	 * @return True if the puzzle list is waiting to be refreshed by an asynchronous call
	 */
	public boolean isRefreshing() {
		return refreshing;
	}

	/**
	 * @return The associated puzzle list information structure
	 */
	public PuzzleListInfo getListInfo() {
		return listInfo;
	}
	
	/**
	 * @return The name of this puzzle list
	 */
	public String getName() {
		if( listInfo == null ) return null;
		return listInfo.getName();
	}
	
	/**
	 * @return The number of puzzles in this list
	 */
	public int getPuzzleCount() {
		if( puzzleList == null || isRefreshing() ) return 0;
		return puzzleList.size();
	}
	
	/**
	 * Access the information for the puzzle at the given index  
	 * 
	 * @param index The index at which to access the puzzle information
	 * @return The information for the accessed puzzle
	 * @throws ArrayIndexOutOfBoundsException 
	 */
	public PuzzleInfo getPuzzle( int index ) throws ArrayIndexOutOfBoundsException  {
		if( refreshing ) throw new ArrayIndexOutOfBoundsException();
		return puzzleList.get(index);
	}
	
	
	/**
	 * Deletes the puzzle at the given index. The deletion is immediately reflected in the
	 * local structure and an asynchronous call is made to update it on the server. If the
	 * call fails, the structure is updated again.
	 * Call notifyObservers, refreshing state may have changed
	 * 
	 * @param index The index of the puzzle to delete
	 * @throws ArrayIndexOutOfBoundsException
	 */
	public void deletePuzzle( int index ) throws ArrayIndexOutOfBoundsException {
		PuzzleInfo puzzleInfo = getPuzzle(index);
		PuzzleServiceAsync puzzleService = ServiceFactory.getPuzzleService(); 		
		puzzleService.deletePuzzleOrTutorial(puzzleInfo.getKey(), deletionCallback );
		
		puzzleList.remove( index );
		setChanged();
	}
	

	
	/**
	 * Sets the list type to retrieve. Make sure you call refresh() at
	 * some point in the future, otherwise the change will not be considered.
	 * 
	 * @param listInfo The information on the list to retrieve
	 */

	public void setListInfo(PuzzleListInfo listInfo) {
		this.listInfo = listInfo;
	}
	
	/**
	 * Refresh the puzzle list from the server using the current list type
	 * Call notifyObservers, refreshing state may have changed
	 */
	public void refresh() {
		if( listInfo == null ) return;
		if( !refreshing ) {
			refreshing = true;
			setChanged();
		}
		listInfo.callService(populateCallback);
	}
	

	private class PopulateCallback implements AsyncCallback< List<PuzzleInfo> > {

		@Override
		public void onSuccess(List<PuzzleInfo> result) {
			refreshing = false;
			puzzleList = result;
			setChanged();
			notifyObservers();
		}

		@Override
		public void onFailure(Throwable caught) {
			refreshing = false;
			puzzleList = null;
			setChanged();
			notifyObservers();
		}

	}


	private class DeletionCallback implements AsyncCallback< Boolean > {

		@Override
		public void onSuccess(Boolean result) {
			if( !result ) failure();
		}

		@Override
		public void onFailure(Throwable caught) {
			failure();
		}
		
		private void failure() {
			refresh();
			notifyObservers();
			Window.alert( "Puzzle deletion failed" );
		}

	}

	@Override
	public void update(Observable observable, Object arg) {	
		refresh();
		notifyObservers();
	}

	@Override
	public void release() {
		super.release();
		
		model.deleteObserver( this );
	}

	
}

